#include "Image.h"

namespace MLoader {

	// -- BMP ------------------------

	bool ImageBMP::ReadFile(const std::string filename)
	{
		FILE *file = fopen(filename.c_str(), "rb");

		if (file)
		{
			unsigned char bmp_file_header[14] = { 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, };
			unsigned char bmp_info_header[40] = { 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0, };
			unsigned char bmp_pad[3] = { 0, 0, 0, };

			memset(bmp_file_header, 0, sizeof(bmp_file_header));
			memset(bmp_info_header, 0, sizeof(bmp_info_header));

			fread(bmp_file_header, sizeof(bmp_file_header), 1, file);
			fread(bmp_info_header, sizeof(bmp_info_header), 1, file);


			if ((bmp_file_header[0] != 'B') || (bmp_file_header[1] != 'M'))
			{
				fclose(file);
				ML_LOG("INVALID SIGNATURE");
				return false;
			}

			if ((bmp_info_header[14] != 24) && (bmp_info_header[14] != 32))
			{
				fclose(file);
				ML_LOG("INVALID BITS PER PIXEL");
				return false;
			}

			_w = (bmp_info_header[4] + (bmp_info_header[5] << 8) + (bmp_info_header[6] << 16) + (bmp_info_header[7] << 24));
			_h = (bmp_info_header[8] + (bmp_info_header[9] << 8) + (bmp_info_header[10] << 16) + (bmp_info_header[11] << 24));
			_data.resize(_w * _h * 3, 0);

			for (int i = 0; i < _h; i++)
			{
				fread(_data.data() + (_w * i * 3), 3, _w, file);
				fread(bmp_pad, 1, (4 - (_w * 3) % 4) % 4, file);
			}

			fclose(file);

			return true;
		}

		return false;
	}

	bool ImageBMP::WriteFile(const std::string filename, const bool vflip, const bool rle) const
	{
		unsigned char bmp_file_header[14] = { 'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, };
		unsigned char bmp_info_header[40] = { 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0, };
		unsigned char bmp_pad[3] = { 0, 0, 0, };

		const int size = 54 + _w * _h * 3;

		bmp_file_header[2] = static_cast<unsigned char>(size);
		bmp_file_header[3] = static_cast<unsigned char>(size >> 8);
		bmp_file_header[4] = static_cast<unsigned char>(size >> 16);
		bmp_file_header[5] = static_cast<unsigned char>(size >> 24);

		bmp_info_header[4] = static_cast<unsigned char>(_w);
		bmp_info_header[5] = static_cast<unsigned char>(_w >> 8);
		bmp_info_header[6] = static_cast<unsigned char>(_w >> 16);
		bmp_info_header[7] = static_cast<unsigned char>(_w >> 24);

		bmp_info_header[8] = static_cast<unsigned char>(_h);
		bmp_info_header[9] = static_cast<unsigned char>(_h >> 8);
		bmp_info_header[10] = static_cast<unsigned char>(_h >> 16);
		bmp_info_header[11] = static_cast<unsigned char>(_h >> 24);

		FILE *file = fopen(filename.c_str(), "wb");

		if (file)
		{
			fwrite(bmp_file_header, 1, 14, file);
			fwrite(bmp_info_header, 1, 40, file);

			for (int i = 0; i < _h; i++)
			{
				fwrite(_data.data() + (_w * i * 3), 3, _w, file);
				fwrite(bmp_pad, 1, ((4 - (_w * 3) % 4) % 4), file);
			}

			fclose(file);

			return true;
		}

		return false;
	}


	// -- TGA ------------------------
	void ImageTGA::flip_horizontally() {
		if (!_data.size()) return;

		int half = _w >> 1;
		for (int i = 0; i < half; i++) {
			for (int j = 0; j < _h; j++) {
				std::swap(*Get(i, j), *Get(_w - 1 - i, j));
			}
		}
	}
	void ImageTGA::flip_vertically() {
		if (!_data.size()) return;

		size_t bytes_per_line = _w * _pps;
		std::vector<std::uint8_t> line(bytes_per_line, 0);
		int half = _h >> 1;
		for (int j = 0; j < half; j++) {
			size_t l1 = j * bytes_per_line;
			size_t l2 = (_h - 1 - j)*bytes_per_line;
			std::copy(_data.begin() + l1, _data.begin() + l1 + bytes_per_line, line.begin());
			std::copy(_data.begin() + l2, _data.begin() + l2 + bytes_per_line, _data.begin() + l1);
			std::copy(line.begin(), line.end(), _data.begin() + l2);
		}
	}

	bool ImageTGA::load_rle_data(std::ifstream &in)
	{
		size_t pixelcount = _w * _h;
		size_t currentpixel = 0;
		size_t currentbyte = 0;
		gm::Color colorbuffer;
		do {
			std::uint8_t chunkheader = 0;
			chunkheader = in.get();
			if (!in.good()) {
				ML_LOG("an error occured while reading the data");
				return false;
			}
			if (chunkheader < 128) {
				chunkheader++;
				for (int i = 0; i < chunkheader; i++) {
					in.read(reinterpret_cast<char *>(&colorbuffer.buffer()), _pps);
					if (!in.good()) {
						ML_LOG("an error occured while reading the data");
						return false;
					}
					for (int t = 0; t < _pps; t++)
						_data[currentbyte++] = colorbuffer[t];
					currentpixel++;
					if (currentpixel > pixelcount) {
						ML_LOG("Too many pixels read");
						return false;
					}
				}
			}
			else {
				chunkheader -= 127;
				in.read(reinterpret_cast<char *>(&colorbuffer.buffer()), _pps);
				if (!in.good()) {
					ML_LOG("an error occured while reading the data");
					return false;
				}
				for (int i = 0; i < chunkheader; i++) {
					for (int t = 0; t < _pps; t++)
						_data[currentbyte++] = colorbuffer.buffer();
					currentpixel++;
					if (currentpixel > pixelcount) {
						ML_LOG("Too many pixels read");
						return false;
					}
				}
			}
		} while (currentpixel < pixelcount);
		return true;
	}
	bool ImageTGA::unload_rle_data(std::ofstream &out) const
	{
		const std::uint8_t max_chunk_length = 128;
		size_t npixels = _w * _h;
		size_t curpix = 0;
		while (curpix < npixels) {
			size_t chunkstart = curpix * _pps;
			size_t curbyte = curpix * _pps;
			std::uint8_t run_length = 1;
			bool raw = true;
			while (curpix + run_length < npixels && run_length < max_chunk_length) {
				bool succ_eq = true;
				for (int t = 0; succ_eq && t < _pps; t++)
					succ_eq = (_data[curbyte + t] == _data[curbyte + t + _pps]);
				curbyte += _pps;
				if (1 == run_length)
					raw = !succ_eq;
				if (raw && succ_eq) {
					run_length--;
					break;
				}
				if (!raw && !succ_eq)
					break;
				run_length++;
			}
			curpix += run_length;
			out.put(raw ? run_length - 1 : run_length + 127);
			if (!out.good()) {
				ML_LOG("can't dump the tga file");
				return false;
			}
			out.write(reinterpret_cast<const char *>(_data.data() + chunkstart), (raw ? run_length * _pps : _pps));
			if (!out.good()) {
				ML_LOG("can't dump the tga file");
				return false;
			}
		}
		return true;
	}

	bool ImageTGA::ReadFile(const std::string filename)
	{
		std::ifstream in;
		in.open(filename, std::ios::binary);
		if (!in.is_open()) {
			ML_LOG("can't open file: %s", filename);
			in.close();
			return false;
		}
		Header header;
		in.read(reinterpret_cast<char *>(&header), sizeof(header));
		if (!in.good()) {
			in.close();
			ML_LOG("an error occured while reading the headers");
			return false;
		}

		_w = header.width;
		_h = header.height;
		_pps = header.bits_per_pixel >> 3;
		if (_w <= 0 || _h <= 0 || (_pps != GRAY && _pps != RGB && _pps != RGBA)) {
			in.close();
			ML_LOG("bad bpp (or width/height) value")
			return false;
		}

		size_t nbytes = _pps * _w * _h;
		_data.resize(nbytes, 0);
		if (3 == header.data_type_code || 2 == header.data_type_code) {
			in.read(reinterpret_cast<char *>(_data.data()), nbytes);
			if (!in.good()) {
				in.close();
				ML_LOG("an error occured while reading the data");
				return false;
			}
		}
		else if (10 == header.data_type_code || 11 == header.data_type_code) {
			if (!load_rle_data(in)) {
				in.close();
				ML_LOG("an error occured while reading the data");
				return false;
			}
		}
		else {
			in.close();
			ML_LOG("unknown file format %ud", header.data_type_code);
			return false;
		}
		if (!(header.image_descriptor & 0x20))
			flip_vertically();
		if (header.image_descriptor & 0x10)
			flip_horizontally();

		ML_LOG("Width: %d, Height: %d, Per pixel size: %d bites", _w, _h, _pps * 8);
		in.close();
		return true;
	}
	bool ImageTGA::WriteFile(const std::string filename, const bool vflip, const bool rle) const
	{
		std::uint8_t developer_area_ref[4] = { 0, 0, 0, 0 };
		std::uint8_t extension_area_ref[4] = { 0, 0, 0, 0 };
		std::uint8_t footer[18] = { 'T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0' };
		std::ofstream out;
		out.open(filename, std::ios::binary);
		if (!out.is_open()) {
			std::cerr << "can't open file " << filename << "\n";
			out.close();
			return false;
		}

		Header header;
		header.bits_per_pixel = _pps << 3;
		header.width = _w;
		header.height = _h;
		header.data_type_code = (_pps == GRAY ? (rle ? 11 : 3) : (rle ? 10 : 2));
		header.image_descriptor = vflip ? 0x00 : 0x20; // top-left or bottom-left origin
		out.write(reinterpret_cast<const char *>(&header), sizeof(header));
		if (!out.good()) {
			out.close();
			ML_LOG("can't dump the tga file");
			return false;
		}
		if (!rle) {
			out.write(reinterpret_cast<const char *>(_data.data()), _w*_h*_pps);
			if (!out.good()) {
				ML_LOG("can't unload raw data");
				out.close();
				return false;
			}
		}
		else {
			if (!unload_rle_data(out)) {
				out.close();
				ML_LOG("can't unload rle data");
				return false;
			}
		}
		out.write(reinterpret_cast<const char *>(developer_area_ref), sizeof(developer_area_ref));
		if (!out.good()) {
			ML_LOG("can't dump the tga file");
			out.close();
			return false;
		}
		out.write(reinterpret_cast<const char *>(extension_area_ref), sizeof(extension_area_ref));
		if (!out.good()) {
			ML_LOG("can't dump the tga file");
			out.close();
			return false;
		}
		out.write(reinterpret_cast<const char *>(footer), sizeof(footer));
		if (!out.good()) {
			ML_LOG("can't dump the tga file");
			out.close();
			return false;
		}
		out.close();
		return true;
	}


	// -- PNG ------------------------

	bool ImagePNG::ReadFile(const std::string filename)
	{
		return false;
	}

	bool ImagePNG::WriteFile(const std::string filename, const bool vflip, const bool rle) const
	{
		return false;
	}

}